﻿using System;
using NCommet.Core.Agents;
using NCommet.Core;
using System.Security.Principal;
using System.Collections.Generic;
using NCommet.Core.Services;

namespace NCommet.Modules.Authorization
{
    /// <summary>
    /// AuthorizationManagerBase implements a part of <see cref="IAuthorizationManager" /> that
    /// is common among many final implementations.
    /// </summary>
    public abstract class AuthorizationManagerBase : IAuthorizationManager
    {
        /// <summary>
        /// Returns the access level the given <paramref name="principal"/> has on the specified <paramref name="item"/>.
        /// </summary>
        /// <remarks>
        /// This method returns the combined access level of all the roles of the given principal on the specified item,
        /// including the access level of the anonymous role. Retrieving the roles of the given principal
        /// requires the existence of <see cref="NCommet.Core.Services.NCommetContainer.WhoAmI" />. If 
        /// <see cref="NCommet.Core.Services.NCommetContainer.WhoAmI" /> is not set, only the anonymous role access level
        /// is returned.
        /// </remarks>
        /// <param name="item">The item for which access level is returned.</param>
        /// <param name="principal">The principal for which access level is returned.</param>
        /// <returns>The access level the given principal has on the specified item.</returns>
        public virtual AccessLevel GetAccessLevel(Item item, IPrincipal principal)
        {
            AccessLevel result = AccessLevel.None;
            if (NCommetContainer.Instance.WhoAmI != null && principal != null && principal.Identity != null && principal.Identity.IsAuthenticated)
            {
                string[] roles = NCommetContainer.Instance.WhoAmI.GetRolesOfUser(principal.Identity.Name);
                if (roles != null && roles.Length > 0)
                    foreach (string role in roles)
                        result |= GetAccessLevel(item, role);
            }
            result |= GetAccessLevel(item, AnonymousRoleName);
            return result;
        }

        /// <summary>
        /// Filters the children of <paramref name="parent"/>, keeping only the items that the given
        /// <paramref name="principal"/> has the required access level.
        /// </summary>
        /// <param name="parent">The item whose children are filtered.</param>
        /// <param name="principal">The principal whose access level on the children items are queried.</param>
        /// <param name="requiredAccessLevel">The access level that the principal must have on the items.</param>
        /// <returns>A list containing items that are children of <paramref name="parent"/>
        /// and the <paramref name="principal"/> has <paramref name="requiredAccessLevel"/> on them.
        /// </returns>
        public virtual IList<Item> FilterChildren(Item parent, IPrincipal principal, AccessLevel requiredAccessLevel)
        {
            List<Item> result = new List<Item>(parent.Children);
            result.RemoveAll(delegate(Item it)
            {
                return (GetAccessLevel(it, principal) & requiredAccessLevel) != requiredAccessLevel;
            });
            return result;
        }

        /// <summary>
        /// Checks if the current logged-in user has the required access level on the specified item.
        /// </summary>
        /// <param name="item">The item on which the current user must have the desired access level.</param>
        /// <param name="accessLevelRequired">The required access level the current user must have on the given item.</param>
        /// <returns>Returns true if the current user exists and has the required access level on the given item.</returns>
        /// <remarks>
        /// If <see cref="NCommet.Core.Services.NCommetContainer.WhoAmI" /> is not set,
        /// the current user cannot be determined and <c>false</c> is always returned.
        /// </remarks>
        public virtual bool HasAccess(Item item, AccessLevel accessLevelRequired)
        {
            return NCommetContainer.Instance.WhoAmI != null ?
                (GetAccessLevel(item, NCommetContainer.Instance.WhoAmI.GetCurrentUser()) & accessLevelRequired) == accessLevelRequired :
                false;
        }

        /// <summary>
        /// The role name of the anonymous user. The default value is <c>string.Empty</c>.
        /// </summary>
        /// <seealso cref="AnonymousRoleName"/>
        protected string anonymousRoleName = string.Empty;

        /// <summary>
        /// The role name of the anonymous user.
        /// The default value is <c>string.Empty</c>.
        /// </summary>
        /// <remarks>
        /// The combined access level a user has on an item always
        /// included the access level of the anonymous role.
        /// </remarks>
        public string AnonymousRoleName
        {
            get
            {
                return anonymousRoleName;
            }
            set
            {
                anonymousRoleName = value;
            }
        }

        public abstract bool IsAccessLevelSet(Item item, string role);

        public abstract AccessLevel GetAccessLevel(Item item, string role);

        public abstract void SetAccessLevel(Item item, string role, AccessLevel accessLevel);

        public abstract void ResetAccessLevel(Item item, string role);

        public abstract string[] RolesWithAccess(Item item, AccessLevel accessLevel);

        public abstract void RolesDeleted(string[] roles);
    }
}
